<?php
namespace App\Services\Payment\PayCore\wechat;

use App\Helpers\SnHelper;
use App\Services\Payment\PayCore\wechat\lib\WxPayData\WxPayCharge;
use App\Services\Payment\PayCore\wechat\lib\WxPayData\WxPayDataBase;
use App\Services\Payment\PayCore\wechat\lib\WxPayData\WxPayResults;
use App\Services\Payment\PayCore\wechat\lib\WxPayData\WxPayUnifiedOrder;
use App\Services\Payment\PayCore\wechat\lib\WxPayData\WxPayRefund;
use App\Services\Payment\PayCore\wechat\lib\WxPayData\WxPayOrderQuery;
use App\Services\Payment\PayCore\wechat\lib\WxPayData\WxPaySignKey;
use App\Services\Payment\PayCore\wechat\lib\WxPayException;
use App\Services\Payment\PayCore\wechat\lib\WxPayNativePay;
use App\Services\Payment\PayCore\wechat\lib\WxPayApi;
use App\Services\Payment\PayCore\wechat\lib\WxPayJsApiPay;
use App\Helpers\XmlUtil;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpKernel\Exception\HttpException;

class WechatPayService
{
    //=======【基本信息设置】=====================================
    //
    /**
     * TODO: 修改这里配置为您自己申请的商户信息
     * 微信公众号信息配置
     *
     * APPID：绑定支付的APPID（必须配置，开户邮件中可查看）
     *
     * MCHID：商户号（必须配置，开户邮件中可查看）
     *
     * KEY：商户支付密钥，参考开户邮件设置（必须配置，登录商户平台自行设置）
     * 设置地址：https://pay.weixin.qq.com/index.php/account/api_cert
     *
     * APPSECRET：公众帐号secert（仅JSAPI支付的时候需要配置， 登录公众平台，进入开发者中心可设置），
     * 获取地址：https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
     * @var string
     */

    public $appid = '';
    public $mchid = '';
    public $key = '';
    public $appSecret = '';

    public $sub_appid = '';
    public $sub_mch_id = '';
    public $sub_key = '';
    //=======【证书路径设置】=====================================
    /**
     * TODO：设置商户证书路径
     * 证书路径,注意应该填写绝对路径（仅退款、撤销订单时需要，可登录商户平台下载，
     * API证书下载地址：https://pay.weixin.qq.com/index.php/account/api_cert，下载之前需要安装商户操作证书）
     * @var path
     */
    public $sslcert_path = '';
    public $sslkey_path = '';
    //=======【curl代理设置】===================================
    /**
     * TODO：这里设置代理机器，只有需要代理的时候才设置，不需要代理，请设置为0.0.0.0和0
     * 本例程通过curl使用HTTP POST方法，此处可修改代理服务器，
     * 默认CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0，此时不开启代理（如有需要才设置）
     * @var unknown_type
     */
    public $curl_proxy_host = "0.0.0.0";//"10.152.18.220";
    public $curl_proxy_port = 0;//8080;
    //=======【上报信息配置】===================================
    /**
     * TODO：接口调用上报等级，默认紧错误上报（注意：上报超时间为【1s】，上报无论成败【永不抛出异常】，
     * 不会影响接口调用流程），开启上报之后，方便微信监控请求调用的质量，建议至少
     * 开启错误上报。
     * 上报等级，0.关闭上报; 1.仅错误出错上报; 2.全量上报
     * @var int
     */
    public $report_levenl = 0;
    public $debug = false;
    public $is_sub_pay = false;
    private $_signKey;

    protected $config = [];

    /**
     * 数据缓存前缀
     * @var string
     */
    public $cachePrefix = 'cache_wechat_pay';

    /**
     * WechatPayService constructor.
     * @param $config
     * @throws \Exception
     */
    public function __construct($config)
    {
        if(empty($config['appid']) && !empty($is_sub_pay)){
            throw new \Exception('appid不能为空');
        }

        if(empty($config['mchid']) && !empty($is_sub_pay)){
            throw new \Exception('mchid不能为空');
        }

        if(empty($config['key']) && !empty($is_sub_pay)){
            throw new \Exception('key不能为空');
        }

        if(empty($config['appSecret']) && !empty($is_sub_pay)){
            throw new \Exception('appSecret不能为空');
        }

        $this->config = $config;

        if(empty($config['is_sub_pay'])){
            $this->config['sslcert_path'] = !empty($config['sslcert_path'])?$config['sslcert_path']:dirname(__FILE__).DIRECTORY_SEPARATOR.'cert'.DIRECTORY_SEPARATOR.'apiclient_cert.pem';
            $this->config['sslkey_path'] = !empty($config['sslkey_path'])?$config['sslkey_path']:dirname(__FILE__).DIRECTORY_SEPARATOR.'cert'.DIRECTORY_SEPARATOR.'apiclient_key.pem';
        }else{
            if(empty($config['sub_appid'])){
                throw new \Exception('sub_appid不能为空');
            }

            if(empty($config['sub_mchid'])){
                throw new \Exception('sub_mchid不能为空');
            }

            if(empty($config['sub_key'])){
                throw new \Exception('sub_key不能为空');
            }

            $this->config['sslcert_path'] = !empty($config['sslcert_path'])?$config['sslcert_path']:dirname(__FILE__).DIRECTORY_SEPARATOR.'subcert'.DIRECTORY_SEPARATOR.'apiclient_cert.pem';
            $this->config['sslkey_path'] = !empty($config['sslkey_path'])?$config['sslkey_path']:dirname(__FILE__).DIRECTORY_SEPARATOR.'subcert'.DIRECTORY_SEPARATOR.'apiclient_key.pem';

            $this->config['sub_appid'] = $config['sub_appid'];
            $this->config['sub_mch_id'] = $config['sub_mchid'];
            $this->config['sub_key'] = $config['sub_key'];
        }

        if(!empty($config['is_sub_pay'])){
            $this->config['appid'] = $config['parent_appid'];
            $this->config['mchid'] = $config['parent_mchid'];
            $this->config['key'] = $config['parent_key'];
            $this->config['appSecret'] = $config['parent_appSecret'];
        }else{
            $this->config['appid'] = $config['appid'];
            $this->config['mchid'] = $config['mchid'];
            $this->config['key'] = $config['key'];
            $this->config['appSecret'] = $config['appSecret'];
        }

        $this->config['debug'] = !empty($config['debug'])?$config['debug']:$this->debug;
        $this->config['is_sub_pay'] = !empty($config['is_sub_pay'])?$config['is_sub_pay']:$this->is_sub_pay;
        $this->config['report_levenl'] = !empty($config['report_levenl'])?$config['report_levenl']:$this->report_levenl;
        $this->config['curl_proxy_host'] = !empty($config['curl_proxy_host'])?$config['curl_proxy_host']:$this->curl_proxy_host;
        $this->config['curl_proxy_port'] = !empty($config['curl_proxy_port'])?$config['curl_proxy_port']:$this->curl_proxy_port;
    }

    /**
     * 获取验签秘钥
     *
     * @param bool $force
     * @return mixed
     * @throws lib\WxPayException
     */
    public function getSignKey($force = false)
    {
        if (empty($this->_signKey) || $force) {
            $result = empty($this->_signKey) && !$force ? $this->getCache('signKey') : false;
            if (!$result) {
                $result = $this->_getSignKey();
                if ($result['code'] != 0) {
                    throw new HttpException(500, $result['msg']);
                }
                $this->setCache('signKey', $result);
            }
            $this->setSignKey($result);
        }
        return $this->_signKey['data'];
    }

    /**
     * 设置signKey
     * @param array $_signKey
     */
    public function setSignKey(array $_signKey)
    {
        $this->_signKey = $_signKey;
    }

    /**
     * 缓存微信数据
     * @param $name
     * @param $value
     * @param int $duration
     * @return bool
     */
    public function setCache($name, $value, $duration = 300)
    {
        return Cache::add($this->getCacheKey($name), $value, $duration);

    }

    /**
     * 获取微信缓存数据
     * @param $name
     * @return mixed
     */
    public function getCache($name)
    {
        $cache_data = Cache::get($this->getCacheKey($name));
        if(!empty($cache_data['expire']) && $cache_data['expire'] < time()){
            Cache::forget($this->getCacheKey($name));
        }
        return $cache_data;
    }

    /**
     * 获取缓存键值
     * @param $name
     * @return string
     */
    protected function getCacheKey($name)
    {
        return $this->cachePrefix . '_' . $this->mchid . '_' . $name;
    }

    /**
     * 获取验签秘钥
     * 商户号      mch_id       微信支付分配的商户号
     * 随机字符串    nonce_str   随机字符串，不长于32位
     * 签名       sign
     *
     * @return array
     * @throws lib\WxPayException
     */
    private function _getSignKey()
    {
        $wechat_config = $this->getWechatConfig();

        $input = new WxPaySignKey($wechat_config);
        $wxPayApi = new WxPayApi($wechat_config);
        $signKey = $wxPayApi::getSignKey($input);

        if(!empty($signKey['return_code']) && $signKey['return_code']!='SUCCESS'){

            return ['code'=>1,'msg'=>$signKey['return_msg']];
        }
        return ['code'=>0,'msg'=>$signKey['return_code'],'data'=>$signKey];
    }

    /**
     * 微信扫码支付
     *
     * @param $order
     * @return array
     * @throws lib\WxPayException
     */
    public function nativePay($order)
    {
        $wechat_config = $this->getWechatConfig();
        if(!empty($data['is_sub_pay'])){
            $wechat_config['is_sub_pay'] = $data['is_sub_pay'];
            $wechat_config['appid'] = $wechat_config['parent_appid'];
            $wechat_config['mchid'] = $wechat_config['parent_mchid'];
            $wechat_config['key'] = $wechat_config['parent_key'];
            $wechat_config['appSecret'] = $wechat_config['parent_appSecret'];
        }

        $input = new WxPayUnifiedOrder($wechat_config);
        $input->SetBody($order['body']);//商品描述
        $input->SetAttach($order['attach']);//附加数据
        $input->SetOut_trade_no($order['out_trade_no']);//商户订单号
        $input->SetTotal_fee($order['total_fee']);//总金额
        $input->SetNotify_url($order['notify_url']);//异步通知地址,不能携带任何参数
        $input->SetTrade_type("NATIVE");//交易类型
        $input->SetProduct_id($order['product_id']);//商品id

        $notify = new WxPayNativePay($wechat_config);
        $result = $notify->GetPayUrl($input);

        if(!empty($result['err_code']) && $result['err_code'] == 'INVALID_REQUEST'){
            return ['code'=>$result['err_code'],'msg'=>$result['err_code_des']];
        }

        if(!empty($result['return_code']) && $result['return_code']=='FAIL'){
            return ['code'=>$result['return_code'],'msg'=>$result['return_msg']];
        }

        if(!empty($result["code_url"])){
            return ['code'=>$result['return_code'],'msg'=>$result['return_msg'],'data'=>$result["code_url"]];
        }else{
            return ['code'=>1,'msg'=>'订单已经支付，支付状态稍后会自动更新'];
        }

    }

    /**
     * 微信JSAPI支付
     *
     * @param $data
     * @return array
     * @throws lib\WxPayException
     */
    public function jsapiPay($data)
    {
        $wechat_config = $this->getWechatConfig();
        if(!empty($data['is_sub_pay'])){
            $wechat_config['is_sub_pay'] = $data['is_sub_pay'];
            $wechat_config['appid'] = $wechat_config['parent_appid'];
            $wechat_config['mchid'] = $wechat_config['parent_mchid'];
            $wechat_config['key'] = $wechat_config['parent_key'];
            $wechat_config['appSecret'] = $wechat_config['parent_appSecret'];
        }

        $input = new WxPayUnifiedOrder($wechat_config);
        $input->SetBody($data['body']);//商品描述
        $input->SetAttach($data['attach']);//附加数据
        $input->SetOut_trade_no($data['out_trade_no']);//商户订单号
        $input->SetTotal_fee($data['total_fee']);//总金额
        $input->SetNotify_url($data['notify_url']);//异步通知地址,不能携带任何参数
        $input->SetTrade_type("JSAPI");//交易类型
        if(!empty($wechat_config['is_sub_pay'])){
            $input->SetSub_openid($data['subOpenid']);
        }else{
            $input->SetOpenid($data['openId']);
        }

        SnHelper::logFun('微信JSAPI支付发起数据',$data);
        $wxPayApi = new WxPayApi($wechat_config);
        $order = $wxPayApi::unifiedOrder($input);
        SnHelper::logFun('微信JSAPI支付发起数据',$order);
        if(!empty($order['err_code']) && $order['err_code'] == 'INVALID_REQUEST'){
            return ['code'=>$order['err_code'],'msg'=>$order['err_code_des']];
        }

        if(!empty($order['return_code']) && $order['return_code']=='FAIL'){
            return ['code'=>$order['return_code'],'msg'=>$order['return_msg']];
        }

        $notify = new WxPayJsApiPay($wechat_config);
        $jsApiParameters = $notify->GetJsApiParameters($order);

        $h5Sign = WxPayApi::H5Sign($order);

        return ['code'=>$order['return_code'],'msg'=>$order['return_msg'],'data'=>$jsApiParameters,'h5sign'=>$h5Sign];


    }

    /**
     * 微信查询订单
     *
     * @param $order
     * @return array
     * @throws lib\WxPayException
     */
    public function orderQuery($order)
    {
        $wechat_config = $this->getWechatConfig();

        $input = new WxPayOrderQuery($wechat_config);
        if(!empty($order["transaction_id"])){
            $input->SetTransaction_id($order["transaction_id"]);
        }

        if(!empty($order["out_trade_no"])){
            $input->SetOut_trade_no($order["out_trade_no"]);
        }

        $wxPayApi = new WxPayApi($wechat_config);
        $res = $wxPayApi::orderQuery($input);
        if(!empty($res['return_code']) && $res['return_code'] !='SUCCESS'){
            return ['code'=>1,'msg'=>$res['return_msg']];
        }

        if(isset($res['err_code'])){
            return ['code'=>1,'msg'=>$res['err_code_des']];
        }

        return ['code'=>0,'msg'=>$res['return_code'],'data'=>$res];

    }

    /**
     * 微信退款
     *
     * @param $order
     * @return array
     * @throws lib\WxPayException
     */
    public function refundOrder($order)
    {
        SnHelper::logFun('微信退款发起数据',$order);

        $wechat_config = $this->getWechatConfig();
        if(!empty($data['is_sub_pay'])){
            $wechat_config['is_sub_pay'] = $data['is_sub_pay'];
            $wechat_config['appid'] = $wechat_config['parent_appid'];
            $wechat_config['mchid'] = $wechat_config['parent_mchid'];
            $wechat_config['key'] = $wechat_config['parent_key'];
            $wechat_config['appSecret'] = $wechat_config['parent_appSecret'];
        }
        $total_fee = $order["total_fee"];
        $refund_fee = $order["refund_fee"];
        $input = new WxPayRefund($wechat_config);
        if(!empty($order["transaction_id"])){
            $input->SetTransaction_id($order["transaction_id"]);
        }

        if(!empty($order["out_trade_no"])){
            $input->SetOut_trade_no($order["out_trade_no"]);
        }
        $input->SetTotal_fee($total_fee);
        if(!empty($order['notify_url'])){
            $input->SetNotify_url($order['notify_url']);
        }
        $input->SetRefund_fee($refund_fee);
        $input->SetOut_refund_no($order['refund_no']);
        $input->SetOp_user_id($this->mchid);

        $wxPayApi = new WxPayApi($wechat_config);
        $refund = $wxPayApi::refund($input);
        SnHelper::logFun('微信退款返回数据',$refund);
        if(!empty($refund['return_code']) && $refund['return_code'] !='SUCCESS'){
            return ['code'=>1,'msg'=>$refund['return_msg']];
        }

        if(isset($refund['err_code'])){
            return ['code'=>1,'msg'=>$refund['err_code_des']];
        }

        return ['code'=>0,'msg'=>$refund['return_code'],'data'=>$refund];
    }

    /**
     * 企业付款到零钱
     * check_name NO_CHECK：不校验真实姓名 FORCE_CHECK：强校验真实姓名
     * 如果check_name设置为FORCE_CHECK，则必填用户真实姓名
     *
     * @param $order
     * @return array
     * @throws lib\WxPayException
     */
    public function changePay($order)
    {
        $wechat_config = $this->getWechatConfig();
        $time = 0;
        $total_fee = $order["total_fee"];
        $check_name = !empty($order["check_name"])?$order["check_name"]:'FORCE_CHECK';
        $re_user_name = !empty($order["re_user_name"])?$order["re_user_name"]:'';
        $desc = !empty($order["desc"])?$order["desc"]:'商家付款到零钱,流水号'.$order["partner_trade_no"];
        $input = new WxPayCharge($wechat_config);
        $input->SetPartner_trade_no($order["partner_trade_no"]);
        $input->SetOpenid($order['openid']);
        $input->SetCheck_name($check_name);
        $input->SetRe_user_name($re_user_name);
        $input->SetAmount($total_fee);
        $input->SetDesc($desc);

        $wxPayApi = new WxPayApi($wechat_config);
        $result = $wxPayApi::changePay($input,15);

        SnHelper::logFun('企业付款到零钱返回数据',json_encode($result, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE).' 提交参数:'.json_encode($order, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        if(!empty($result['return_code']) && !empty($result['result_code'])){
            if($result['return_code'] == 'SYSTEMERROR' || $result['result_code'] == 'SYSTEMERROR'){
                if($time < 2){
                    $time++;
                    return $this->changePay($order);
                }
            }

            if($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS'){
                return ['code'=>0,'msg'=>'操作成功','data'=>$result];
            }elseif($result['return_code'] == 'FAIL' && $result['result_code'] == 'FAIL'){
                return ['code'=>1,'msg'=>$result['return_msg'].';'.$result['err_code_des']];
            }elseif($result['return_code'] == 'FAIL'){
                return ['code'=>1,'msg'=>$result['return_msg']];
            }elseif($result['result_code'] == 'FAIL'){
                //后台提现次数限制提示：'该用户今日提现到帐次数超过10次，请次日进行打款操作。'
                //会员端提现次数限制提示：'微信官方限制：每日提现不能超过10次，请次日重试。'
                if($result['err_code'] == 'SENDNUM_LIMIT'){
                    if(empty($order['handle'])){//会员端
                        return ['code'=>2,'msg'=>'微信官方限制：每日提现不能超过10次，请次日重试。'];
                    }else{
                        return ['code'=>2,'msg'=>'该用户今日提现到帐次数超过10次，请次日进行打款操作。'];
                    }
                }elseif($result['err_code'] == 'V2_ACCOUNT_SIMPLE_BAN'){//账号为实名认证
                    return ['code'=>3,'msg'=>$result['err_code_des']];
                }

                return ['code'=>1,'msg'=>$result['err_code_des']];
            }else{
                return ['code'=>1,'msg'=>$result['err_code_des']];
            }
        }
        return ['code'=>1,'msg'=>'操作失败'];

    }


    /**
     * 微信支付异步回调
     *
     * @param Request $request
     * @return array
     * @throws WxPayException
     */
    public function notify(Request $request)
    {
        //获取通知的数据
        $xml = $request->getContent();
        SnHelper::logFun('微信支付异步回调',$xml);

        $wechat_config = $this->getWechatConfig();
        if(!empty($wechat_config['debug'])){
            return ['code'=>0,'data'=>XmlUtil::FromXml($xml)];
        }else{
            //验证签名
            try {
                new WxPayDataBase($wechat_config);
                $result = WxPayResults::Init($xml);
            } catch (WxPayException $e){
                return ['code'=>1,'msg'=>$e->errorMessage()];
            }

            //查询订单合法性
            $data['transaction_id'] = !empty($result['transaction_id'])?$result['transaction_id']:'';
            $res = $this->orderQuery($data);

            if(empty($res['code']))
            {
                return ['code'=>0,'data'=>$res];
            }
            return ['code'=>1,'msg'=>$res['msg']];
        }
    }

    /**
     * 微信退款异步回调
     *
     * @param Request $request
     * @return array
     * @throws WxPayException
     */
    public function refundNotify(Request $request)
    {
        //获取通知的数据
        $xml = $request->getContent();
        SnHelper::logFun('微信退款异步回调',$xml);

        $wechat_config = $this->getWechatConfig();
        if(!empty($wechat_config['debug'])){
            return ['code'=>0,'data'=>XmlUtil::FromXml($xml)];
        }else{
            $xmlArr=(new WxPayDataBase($wechat_config))->FromXml($xml);

            //验证签名
            $decrypt = base64_decode($xmlArr["req_info"], true);
            $decode = openssl_decrypt($decrypt , 'aes-256-ecb', md5($wechat_config['key']), OPENSSL_RAW_DATA);
            if(empty($decode)){
                return ['code'=>1,'msg'=>'签名失败'];
            }

            $xmlArr=(new WxPayDataBase($wechat_config))->FromXml($decode);
            return ['code'=>0,'data'=>$xmlArr];
        }
    }

    public function getWechatConfig()
    {
        return $this->config;
    }

}